namespace Guia.Locator
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Windows.Automation;
    using Guia.Core;
    using Guia.Elements;

    /// <summary>
    /// This class capsulates the finding and instantiating of UiElements
    /// </summary>
    public static class UiElementFactory
    {
        /// <summary>
        /// Gets a descendant UiElement form the given parent UiElement that matches the condition
        /// </summary>
        /// <param name="parent">the parent UiElement</param>
        /// <param name="condition">the SearchCondition</param>
        /// <returns>the first found UiElement</returns>
        /// <exception cref="ElementNotFoundException"/>
        /// <exception cref="ArgumentNullException">This exception is thrown if parent or condition is null.</exception>
        public static UiElement GetUiElement(UiElement parent, SearchCondition condition)
        {
            Contract.Requires<ArgumentNullException>(parent != null);
            Contract.Requires<ArgumentNullException>(condition != null);
            AutomationElement automationElement = AutomationElementFinder.FindFirst(parent, condition);
            UiElement uiElement = new UiElement(automationElement);
            return uiElement;
        }

        /// <summary>
        /// Gets all descendant UiElements form the given parent UiElement which match the condition
        /// </summary>
        /// <param name="parent">the parent UiElement</param>
        /// <param name="condition">the SearchCondition</param>
        /// <returns>all found UiElements</returns>
        /// <exception cref="ElementNotFoundException"/>
        /// <exception cref="ArgumentNullException">This exception is thrown if parent or condition is null.</exception>
        public static UiElement[] GetAllUiElements(UiElement parent, SearchCondition condition)
        {
            Contract.Requires<ArgumentNullException>(parent != null);
            Contract.Requires<ArgumentNullException>(condition != null);
            List<UiElement> uiElements = new List<UiElement>();
            try
            {
                AutomationElementCollection automationElementCollection = AutomationElementFinder.FindAll(parent, condition);
                foreach (AutomationElement automationElement in automationElementCollection)
                {
                    uiElements.Add(new UiElement(automationElement));
                }
            }
            catch (ElementNotFoundException)
            {
            }

            return uiElements.ToArray();
        }

        /// <summary>
        /// Gets all descendant UiElements from the given UiElement
        /// </summary>
        /// <param name="parent">The parent element</param>
        /// <returns>All descendat UiElements</returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if parent is null.</exception>
        public static UiElement[] GetAllUiElements(UiElement parent)
        {
            Contract.Requires<ArgumentNullException>(parent != null);
            List<UiElement> uiElements = new List<UiElement>();
            try
            {
                AutomationElementCollection automationElementCollection = AutomationElementFinder.FindAll(parent);
                foreach (AutomationElement automationElement in automationElementCollection)
                {
                    uiElements.Add(new UiElement(automationElement));
                }
            }
            catch (ElementNotFoundException)
            {
            }

            return uiElements.ToArray();
        }

        /// <summary>
        /// Gets a child UiElement with a specified type form the given parent UiElement that matches the condition
        /// </summary>
        /// <typeparam name="T">Type of the UiElement</typeparam>
        /// <param name="parent">the parent UiElement</param>
        /// <param name="condition">the SearchCondition</param>
        /// <returns>the first found UiElement</returns>
        /// <exception cref="ElementNotFoundException"/>
        /// <exception cref="ArgumentNullException">This exception is thrown if parent or condition is null.</exception>
        public static T GetUiElement<T>(UiElement parent, SearchCondition condition) where T : UiElement
        {
            Contract.Requires<ArgumentNullException>(parent != null);
            Contract.Requires<ArgumentNullException>(condition != null);
            SearchCondition controlTypeCondition = ControlTypeDirectory.GetConditionForType<T>();
            SearchCondition searchCondition = condition.And(controlTypeCondition);
            AutomationElement automationElement = AutomationElementFinder.FindFirst(parent, searchCondition);
            T element =
                (T)
                typeof(T).
                    GetConstructor(
                        new Type[] 
                        {
                            typeof(AutomationElement)
                        })
                    .Invoke(
                        new object[]
                        {
                            automationElement
                        });
            return element;
        }

        /// <summary>
        /// Gets all child UiElements with a specified type form the given parent UiElement which match the condition
        /// </summary>
        /// <typeparam name="T">Type of the UiElement</typeparam>
        /// <param name="parent">the parent UiElement</param>
        /// <param name="condition">the SearchCondition</param>
        /// <returns>all found UiElements</returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if parent or condition is null.</exception>
        public static T[] GetAllUiElements<T>(UiElement parent, SearchCondition condition) where T : UiElement
        {
            Contract.Requires<ArgumentNullException>(parent != null);
            Contract.Requires<ArgumentNullException>(condition != null);
            List<T> uiElements = new List<T>();
            SearchCondition controlTypeCondition = ControlTypeDirectory.GetConditionForType<T>();
            SearchCondition searchCondition = condition.And(controlTypeCondition);
            try
            {
                AutomationElementCollection automationElementCollection = AutomationElementFinder.FindAll(parent, searchCondition);
                foreach (AutomationElement automationElement in automationElementCollection)
                {
                    T element =
                        (T)
                        typeof(T).
                            GetConstructor(
                                new Type[]
                                    {
                                        typeof(AutomationElement)
                                    }).
                                Invoke(new object[]
                                    {
                                        automationElement
                                    });
                    uiElements.Add(element);
                }
            }
            catch (ElementNotFoundException)
            {
            }

            return uiElements.ToArray();
        }

        /// <summary>
        /// Gets all descendant UiElements from the given UiElement from the specified type
        /// </summary>
        /// <typeparam name="T">Type of the wanted UiElements</typeparam>
        /// <param name="parent">The parent element</param>
        /// <returns>All descendant UiElements from the given type</returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if parent is null.</exception>
        public static T[] GetAllUiElements<T>(UiElement parent) where T : UiElement
        {
            Contract.Requires<ArgumentNullException>(parent != null);
            List<T> uiElements = new List<T>();
            SearchCondition controlTypeCondition = ControlTypeDirectory.GetConditionForType<T>();
            try
            {
                AutomationElementCollection automationElementCollection = AutomationElementFinder.FindAll(parent, controlTypeCondition);
                foreach (AutomationElement automationElement in automationElementCollection)
                {
                    T element =
                        (T)
                        typeof(T).
                            GetConstructor(
                                new Type[]
                                    {
                                        typeof(AutomationElement)
                                    }).
                                Invoke( 
                                    new object[]
                                {
                                    automationElement
                                });
                    uiElements.Add(element);
                }
            }
            catch (ElementNotFoundException)
            {  
            }

            return uiElements.ToArray();
        }
    }
}